package cn.bearspark.happening.provider.dao.buffer;

import cn.bearspark.framework.redis.starter.buffer.hash.HotData;
import cn.bearspark.framework.redis.starter.buffer.hash.un.RedisHashBuffer;
import cn.bearspark.common.enumeration.VisibilityEnum;
import cn.bearspark.happening.provider.dao.co.HappeningCO;
import cn.bearspark.happening.provider.dao.key.HappeningBufferKeyBuilder;
import cn.bearspark.happening.provider.dao.mapper.HappeningBaseMapper;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 动态数据的缓存器
 *
 * @author f
 */
@Component
public class HappeningHashBuffer extends RedisHashBuffer<HappeningCO> {

    /**
     * 公开的可见性的枚举值
     */
    private static final int PUBLIC_CODE = VisibilityEnum.PUBLIC.getCode();

    /**
     * 设置动态数据的缓存的有效期（单位：s），默认是 10min
     */
    private static final int HAPPENING_INFO_CACHE_EXPIRE = 60 * 10;

    /**
     * 从数据库中查不到的动态缓存对象的生成器
     */
    private static final Function<List<Long>, Map<Long, HappeningCO>> DELETED_CO_GENERATOR =
            happeningIdList -> happeningIdList.stream().collect(Collectors.toMap(
                    Function.identity(),
                    happeningId -> {
                        HappeningCO happeningCO = new HappeningCO();
                        // 设置 id
                        happeningCO.setHappeningId(happeningId);
                        // 设置 热数据
                        happeningCO.setLikeNum(0L);
                        happeningCO.setCommentNum(0L);
                        happeningCO.setForwardNum(0L);
                        return happeningCO;
                    }
            ));

    /**
     * 删除时需要清理的字段
     */
    private final Object[] deleteFieldsName;

    private final HappeningBaseMapper happeningBaseMapper;

    private final HappeningBufferKeyBuilder happeningBufferKeyBuilder;

    public HappeningHashBuffer(RedisTemplate<String, Object> redisTemplate,
                               HappeningBufferKeyBuilder happeningBufferKeyBuilder,
                               HappeningBaseMapper happeningBaseMapper) {
        super(
                redisTemplate,
                happeningBufferKeyBuilder,
                HAPPENING_INFO_CACHE_EXPIRE,
                HappeningCO.class,
                happeningBaseMapper,
                DELETED_CO_GENERATOR
        );
        this.happeningBaseMapper = happeningBaseMapper;
        this.happeningBufferKeyBuilder = happeningBufferKeyBuilder;

        // 获取删除时需要清理的字段
        List<Object> deleteFieldNameList = new ArrayList<>();
        Field[] fields = HappeningCO.class.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            if (field.isAnnotationPresent(HotData.class)) {
                continue;
            }
            String fieldName = field.getName();
            if (!fieldName.equals("id")) {
                deleteFieldNameList.add(fieldName);
            }
        }
        this.deleteFieldsName = deleteFieldNameList.toArray();
    }

    /**
     * 查询指定动态的缓存
     *
     * @param happeningIdList 指定的动态 id 集合
     * @return 指定动态的缓存集合
     */
    public List<HappeningCO> listHappeningCO(Collection<Long> happeningIdList) {
        Map<Long, HappeningCO> happeningCOMap = super.map(happeningIdList);
        return happeningIdList.stream().map(happeningCOMap::get).filter(Objects::nonNull).toList();
    }

    /**
     * "删除" 缓存中的动态数据
     *
     * @param happeningId 动态的 id
     */
    public void deleteOddly(long happeningId) {
        // 清理动态缓存的 除 动态 id 和 热数据 之外 的字段，防止 RedisBuffer 在数据库查询数据，等待这条动态自动过期
        redisTemplate.opsForHash().delete(happeningBufferKeyBuilder.buildHashBufferKey(happeningId), deleteFieldsName);
    }

    /**
     * 获取指定动态缓存对象
     *
     * @param userId      获取动态用户 id
     * @param happeningId 指定动态的 id
     * @return 指定动态的缓存对象，如果用户的权限不够，则返回 null
     */
    public HappeningCO getOddly(long userId, long happeningId) {
        if (happeningBaseMapper.count(userId, happeningId, PUBLIC_CODE) == 0) {
            return null;
        }
        return super.get(happeningId);
    }
}
